/*
laliangdang * Copweimingyright (c) 2023 Thead CO., LIMITED.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <cstdlib>
#include <map>
#include <mutex>
#include <thread>
#include <vector>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/eventfd.h>
#include "camera_test.h"
#include "welog.h"

struct CameraParam camParam = {
    .channel_id = 0,
    .video_id = 2,
    .dump_enable = 1,
    .display_enable = 0,
    .width = 640,
    .height = 480,
    .fmt = CSI_PIX_FMT_NV12,
    .frame_num = 30,
    .chn_id = CSI_CAMERA_CHANNEL_0,
    .env_type = CSI_CAMERA_EVENT_TYPE_CHANNEL0,
    .dev_name = "/dev/video2",
    .camera_cfg = {
        .mode_id = 1,
        .calibriation = NULL,
        .lib3a = NULL,
    },
};

static void CameraFrameHandle(csi_frame_ex_s *frame, int num)
{
    int i;
    int fd = -1;
    char image_name[128] = { 0 };
    int width = frame->frame_info.width;
    int height = frame->frame_info.height;
    void *ptr = frame->frame_data.vir_addr[0];
    void *dst_addr = malloc(width * height * 3 / 2);
    if (dst_addr == nullptr) {
        welog_err("malloc failed");
        return;
    }
    void *tmp = dst_addr;

    welog_info("width = %d, height = %d", width, height);
    if (frame->frame_info.pixel_format != CSI_PIX_FMT_NV12) {
        welog_err("currently do not surpport this format %d", frame->frame_info.pixel_format);
        return;
    }

    for (i = 0; i < height; i++) {
        memcpy(dst_addr, ptr, width);
        dst_addr = (void *)((uint64_t)dst_addr + width);
        ptr = (void *)((uint64_t)ptr + frame->frame_data.stride[0]);
    }

    ptr = frame->frame_data.vir_addr[1];
    for (i = 0; i < height / 2; i++) {
        memcpy(dst_addr, ptr, width);
        dst_addr = (void *)((uint64_t)dst_addr + width);
        ptr = (void *)((uint64_t)ptr + frame->frame_data.stride[1]);
    }

    sprintf(image_name, "/data/misc/camera/welog_images_%d.nv12", num);
    welog_info("start copy data to file: %s", image_name);
    fd = open(image_name, O_CREAT | O_RDWR);
    if (fd < 0) {
        welog_err("create image failed, fd = %d, num = %d", fd, num);
        return;
    }

    write(fd, tmp, width * height * 3 / 2);
    welog_info("copy data to file success");

    close(fd);
    free(tmp);
}

static void loopBuffers()
{
    int i = 0;
    struct csi_camera_event event;
    csi_frame_ex_s frame;
    int timeout = 10; // unit: ms, -1 means wait forever, or until error occurs
    bool running = true;
    while (running) {
        if (i == 10) {
            running = false;
        }
        if(CameraGetEvent(camParam.event_handle, &event, timeout)) {
            continue;
        }
        if (event.id == CSI_CAMERA_CHANNEL_EVENT_FRAME_READY) {
            int read_frame_count = CameraGetFrameCount(camParam.cam_handle, camParam.chn_id);
            for (int j = 0; j < read_frame_count; j++) {
                if (CameraGetFrame(camParam.cam_handle, camParam.chn_id, &frame, timeout)) {
                    continue;
                }

                welog_info("handle frame %d", i);
                CameraFrameHandle(&frame, i++);

                CameraPutFrame(&frame);
            }
        }
    }
}

int main(void)
{    
    welog_info("start camera...");
    int ret;
    ret = CameraOpen(&camParam.cam_handle, camParam.dev_name);
    if (ret) {
        welog_err("camera open failed, ret = %d", ret);
        return -1;
    }

    ret = CameraSetMode(camParam.cam_handle, &camParam.camera_cfg);
    if (ret) {
        welog_err("camera set mode failed, ret = %d", ret);;
        return -1;
    }

    csi_camera_property_s property[] = {
        {
            .id = CSI_CAMERA_PID_HFLIP,
            .type = CSI_CAMERA_PROPERTY_TYPE_BOOLEAN,
            .value.bool_value = false,
        },
        {
            .id = CSI_CAMERA_PID_VFLIP,
            .type = CSI_CAMERA_PROPERTY_TYPE_BOOLEAN,
            .value.bool_value = false,
        },
        {
            .id = CSI_CAMERA_PID_ROTATE,
            .type = CSI_CAMERA_PROPERTY_TYPE_INTEGER,
            .value.int_value = false,
        },
    };
    csi_camera_properties_s properties = {
        .count = sizeof(property) / sizeof(property[0]),
        .property = property,
    };

    ret = CameraSetProperty(camParam.cam_handle, &properties);
    if (ret) {
        welog_debug("camera set property failed, ret = %d", ret);
    }

    camParam.chn_cfg.chn_id = camParam.chn_id,
    CameraChannelQuery(camParam.cam_handle, &camParam.chn_cfg);
    if (camParam.chn_cfg.status != CSI_CAMERA_CHANNEL_CLOSED) {
        welog_err("camera channel query failed, chn_id = %d", camParam.chn_cfg.chn_id);
        return -1;
    }

    camParam.chn_cfg.chn_id = camParam.chn_id;
    camParam.chn_cfg.frm_cnt = 1;
    camParam.chn_cfg.img_fmt.width = camParam.width;
    camParam.chn_cfg.img_fmt.height = camParam.height;

    camParam.chn_cfg.img_fmt.pix_fmt = camParam.fmt;
    camParam.chn_cfg.img_type = CSI_IMG_TYPE_DMA_BUF;
    camParam.chn_cfg.meta_fields = CSI_CAMERA_META_DEFAULT_FIELDS;
    camParam.chn_cfg.capture_type = CSI_CAMERA_CHANNEL_CAPTURE_VIDEO | CSI_CAMERA_CHANNEL_CAPTURE_META;
    ret = CameraChannelOpen(camParam.cam_handle, &camParam.chn_cfg);
    if (ret) {
        welog_err("camera channel open failed, ret = %d", ret);
        return -1;
    }

    CameraCreateEvent(&camParam.event_handle, camParam.cam_handle);
    camParam.subscribe.type = CSI_CAMERA_EVENT_TYPE_CAMERA;
    camParam.subscribe.id = CSI_CAMERA_EVENT_WARNING | CSI_CAMERA_EVENT_ERROR;
    CameraSubscribeEvent(camParam.event_handle, &camParam.subscribe);
    camParam.subscribe.type = camParam.env_type;
    camParam.subscribe.id = CSI_CAMERA_CHANNEL_EVENT_FRAME_READY | CSI_CAMERA_CHANNEL_EVENT_OVERFLOW;
    CameraSubscribeEvent(camParam.event_handle, &camParam.subscribe);

    ret = CameraChannelStart(camParam.cam_handle, camParam.chn_id);
    if (ret) {
        welog_err("camera channel start failed, ret = %d", ret);
        return -1;
    }

    loopBuffers();

    welog_info("camera start stop...");
    ret = CameraChannelStop(camParam.cam_handle, camParam.chn_id);
    if (ret) {
        welog_err("camera channel stop failed, ret = %d", ret);
        return -1;
    }
    usleep (100);

    camParam.subscribe.type = CSI_CAMERA_EVENT_TYPE_CHANNEL0;
    camParam.subscribe.id = CSI_CAMERA_CHANNEL_EVENT_FRAME_READY;
    ret = CameraUnsubscribeEvent(camParam.event_handle, &camParam.subscribe);
    if (ret) {
        welog_err("camera unsubscribe event failed, ret = %d", ret);
        return -1;
    }
    ret = CameraDestoryEvent(camParam.event_handle);
    if (ret) {
        welog_err("camera destory event failed, ret = %d", ret);
        return -1;
    }
    ret = CameraChannelClose(camParam.cam_handle, camParam.chn_id);
    if (ret) {
        welog_err("camera channel close failed, ret = %d", ret);
        return -1;
    }
    ret = CameraClose(camParam.cam_handle);
    if (ret) {
        welog_err("camera close failed, ret = %d", ret);
        return -1;
    }

    return 0;
}
